package top.yazif.excel;

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.AreaReference;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import top.yazif.exception.ExcelException;
import top.yazif.exception.ExcelIndexOutOfBoundsException;

import java.io.*;

/**
 * Created by Yazif on 2017/3/25.
 */
public class BigExcelImpl implements Closeable{
    private SXSSFWorkbook workbook;//Excel工作簿
    private SXSSFSheet sheet;//Excel表
    private int rowAccessWindowSize;
    private File file;//Excel保存
    private File tmp;//Excel缓存文件


    public BigExcelImpl(){
        workbook = new SXSSFWorkbook();
        sheet = workbook.createSheet();
    }

    public BigExcelImpl(int rowAccessWindowSize){
        this.rowAccessWindowSize = rowAccessWindowSize;
        workbook = new SXSSFWorkbook(rowAccessWindowSize);
    }
    /**
     * @deprecated
     * @param path
     * @param rowAccessWindowSize
     * @throws ExcelException
     */
    public BigExcelImpl(String path, int rowAccessWindowSize)throws ExcelException {
        file = new File(path);
        this.rowAccessWindowSize = rowAccessWindowSize;
        open();
    }

    /**
     * 通过工作表的索引设置当前工作的工作表
     * @param index 工作表的索引
     * @throws ExcelIndexOutOfBoundsException
     * @throws ExcelException
     *
     * @deprecated
     */
    public void setSheet(int index) throws ExcelIndexOutOfBoundsException, ExcelException {
        if(workbook == null){
            throw new ExcelException("工作表为空");
        }
        if(index<workbook.getNumberOfSheets()&&index>=0){
            throw new ExcelIndexOutOfBoundsException(String.valueOf(index));
        }

        sheet= workbook.getSheetAt(index);
    }

    /**
     * 通过工作表的名称设置当前工作的工作表
     * 当工作表的名称不存在时抛出<code>ExcelException</code>异常
     * @param name  工作表名称
     * @throws ExcelException
     *
     * @deprecated
     */
    public void setSheet(String name) throws ExcelException {
        sheet = workbook.getSheet(name);
        if(sheet == null){
            throw new ExcelException("无工作表的名称为： " + name);
        }
    }

    /**
     * 获取当前工作的行数
     * @return
     *
     * @deprecated
     *
     * remove
     */
    public int getNumberOfRows(){
        return sheet.getLastRowNum()+1;
    }


    /**
     * 获取行所拥有的单元格数
     * @param rowIndex 行索引
     * @return 行所拥有的单元格数
     * @throws ExcelIndexOutOfBoundsException
     */
    public int getNumberOfCell(int rowIndex) throws ExcelIndexOutOfBoundsException {
        if(rowIndex < 0){
            throw new ExcelIndexOutOfBoundsException( String.valueOf(rowIndex));
        }
        Row row = sheet.getRow(rowIndex);
        int num = -1;
        if(row != null){
            num = row.getLastCellNum() +1;
        }
        return num;
    }
    /**
     * 判断当前工作表索引为<code>row</code>的行是否激活
     * 激活返回<code>true</code>
     * 否则返回<code>false</code>
     * @param row
     * @return
     */
    public boolean isRowActive(int row){
        return (sheet.getRow(row)) == null ? false  : true;
    }

    /**
     * 将工作簿另存到<code>file</code>中
     * @param file 保存的文件的地址
     * @throws IOException
     */
    public void save(String file) throws IOException {
        try(OutputStream fout = new FileOutputStream(file)){
            workbook.write(fout);
        }
    }

    /**
     * 通过工作表的行和列获取当前工作表的单元格的值
     * 当单元格未被创建时，单元格将被创建，并取得空值
     * @param row 行索引
     * @param col 列索引
     * @return 当前工作表行索引为<code>row</code>列索引为<code>col</code>的单元格的值
     */
    public String getValue(int row,int col){
        String value = null;
        Cell cell =getCell(row,col);
        value = readCell(cell);
        return value;
    }

    public void addSheet(String name) throws ExcelException {
        if(workbook.getName(name) != null){
            throw new ExcelException("已存在");
        }
        workbook.createSheet(name);
    }

    /**
     * 获取工作簿里名称为<code>name</code>单元格的值
     * <code>name</code>所指定的单元域必须是连续的
     * 当指定的名称<code>name</code为单元格域时，返回该域第一个单元格的值
     * 当指定的名称<code>name</code>不存在时，抛出<code>ExcelException</code>异常
     * @param name 单元格的名称
     * @return 对应单元格名称的值
     * @throws ExcelException
     */
    public String getValue(String name) throws ExcelException {
//        AreaReference areaReference = new AreaReference(getName(name).getRefersToFormula(), workbook.getSpreadsheetVersion());
//        if(!areaReference.isSingleCell()){
//            throw new ExcelException("书签存在多个单元格： " + name);
//        }
//
//        Cell cell = getCellByName(areaReference.getFirstCell());
//        String value = readCell(cell);

        return getValues(name)[0];
    }

    /**
     * 获取工作簿里名称为<code>name</code>单元域的值
     * <code>name</code>所指定的单元域必须是连续的
     * 当指定的名称<code>name</code>不存在时，抛出<code>ExcelException</code>异常
     * @param name 单元域的名称
     * @return 名称为<code>name</code>单元域的值
     * @throws ExcelException
     */
    public String[] getValues(String name) throws ExcelException {
        AreaReference area = new AreaReference(getName(name).getRefersToFormula(), workbook.getSpreadsheetVersion());
        CellReference[] cellReference = area.getAllReferencedCells();
        String[] values = new String[cellReference.length];

        int index = 0;
        for(CellReference cell : cellReference){
            values[index++] = readCell(getCellByName(cell));
        }

        return values;
    }

    /**
     * 通过工作表的行和列设置当前工作表的单元格的值
     * @param value 要设置的值
     * @param row 行索引
     * @param col 列索引
     */
    public void setCellValue(String value, int row, int col){
        Cell cell = getCell(row, col);
        cell.setCellValue(String.valueOf(value));
    }

    /**
     * 设置工作簿里名称为<code>name</code>单元域的值
     * <code>name</code>所指定的单元域必须是连续的
     * 当指定的名称<code>name</code>为单元格域时，则设置该域第一个单元格的值
     * 当指定的名称<code>name</code>不存在时，抛出<code>ExcelException</code>异常
     * @param value 要设置的值
     * @param name 单元格的名称
     * @throws ExcelException
     */
    public void setCellValue(String value, String name) throws ExcelException {
        setCellValues(new String[]{ value }, name);
    }

    /**
     * 设置工作簿里名称为<code>name</code>单元域的值
     * <code>name</code>所指定的单元域必须是连续的
     * 填充方式为“从左至右，从上至下”
     * 当<code>values</code>的长度大于单元域的长度时，<code>values</code>将被截断
     * 当<code>values</code>的长度小于单元域的长度时，<code>values</code>将从头开始填充
     * 当指定的名称<code>name</code>不存在时，抛出<code>ExcelException</code>异常
     * @param values 要设置的值
     * @param name 单元域的名称
     * @throws ExcelException
     */
    public void setCellValues(String[] values, String name) throws ExcelException {
        AreaReference area = new AreaReference(getName(name).getRefersToFormula(), workbook.getSpreadsheetVersion());
        CellReference[] cellReference = area.getAllReferencedCells();

        int cellCount = cellReference.length;
        int stringCount = values.length;
        for(int index = 0; index < cellCount && index < stringCount; index ++){
            getCellByName(cellReference[index]).setCellValue(values[index]);
        }
    }

    /**
     * 通过行索引和列索引获取单元格
     * 当单元格不存在时，单元格将被新建
     * @param row
     * @param col
     * @return
     */
    private Cell getCell(int row,int col){
        Cell cell = null;
        Row r = null;
        if(sheet != null && row >= 0 && col >= 0 && row < sheet.getLastRowNum() + 1){
            r=sheet.getRow(row);
        }
        if(r == null){
            r = sheet.createRow(row);
        }
        cell = r.getCell(col);
        if(cell == null) {
            cell = r.createCell(col);
            cell.setCellValue("");
        }
        return cell;
    }

    /**
     * 这是一个通过<code>CellReference</code>获取cell的函数
     * @param cellReference  Name信息
     * @return  含有Name的cell
     */
    private Cell getCellByName(CellReference cellReference){
        Cell cell = null;

        //通过Name中的关联地址找到cell
//        CellReference cellReference = new CellReference(name.getRefersToFormula());//找到cell在workbook中的位置
        Sheet sheet = workbook.getSheet(cellReference.getSheetName());   //获取cell所在的sheet
        Row row = sheet.getRow(cellReference.getRow());

        if(row == null){//如果获取的行为空，创建该行
            row = sheet.createRow(cellReference.getRow());
        }

        cell = row.getCell(cellReference.getCol());
        if(cell == null){//如果获取的cell为空，创建改cell
            cell = row.createCell(cellReference.getCol());
        }
        return cell;
    }

    /**
     * 通过<code>name</code>获取工作簿中的<code>Name</code>
     * 当<code>Name</code>不存在时抛出异常
     * @param name
     * @return
     * @throws ExcelException
     *
     * @deprecated
     */
    private Name getName(String name) throws ExcelException {
        Name cellName =  workbook.getName(name);
        if(cellName == null){
            throw new ExcelException("工作簿不存在书签： " + name);
        }
        return cellName;
    }


    /**
     * 根据单元格类型获取单元格的值
     * @param cell
     * @return
     */
    private String readCell(Cell cell){
        String value="";
        if(cell==null){
            //不做处理
        }
        else if(cell.getCellTypeEnum() == CellType.BLANK){
        }
        else if(cell.getCellTypeEnum() == CellType.NUMERIC){
            value=String.valueOf((long)cell.getNumericCellValue());
        }
        //空值
        //字符串
        else if(cell.getCellTypeEnum() == CellType.STRING){
            value = String.valueOf(String.valueOf(cell));
        }
        //布尔
        else if(cell.getCellTypeEnum() == CellType.BOOLEAN){
            value=String.valueOf(cell.getBooleanCellValue());
        }
        //公式
        else if(cell.getCellTypeEnum() == CellType.FORMULA){
            value = String.valueOf(cell.getCellFormula());
        }
        return value;
    }



    /**
     * 创建一个缓存文件
     * @param file
     * @return
     * @throws IOException
     *
     * @deprecated
     */
    private File backupFile(File file) throws IOException {
        File tmp = File.createTempFile(file.getName(),".tmp", file.getParentFile());
        try(FileInputStream fin = new FileInputStream(file.getAbsolutePath()); FileOutputStream fout =  new FileOutputStream(tmp.getAbsolutePath())){
            byte[] byt = new byte[fin.available()];
            fin.read(byt);
            fout.write(byt);
        }
        return tmp;
    }

    @Override
    /**
     * 关闭并保存工作簿
     */
    public void close() throws IOException {

        if (!isClosed()) {
//            try (OutputStream fout = new FileOutputStream(file.getAbsolutePath())) {
//                if (file.canWrite())
//                    workbook.write(fout);
//            }
            workbook.close();
//            tmp.delete();
            workbook = null;
        }
    }

    /**
     * 判断excel工作簿是否关闭
     * @return 关闭返回<code>true</code>， 否则返回<code>false</code>
     */
    public boolean isClosed(){
        boolean closed = false;
        if(workbook == null){
            closed = true;
        }
        return closed;
    }

    /**
     * 打开工作簿
     * @deprecated
     */
    public void open() throws ExcelException {
        if(isClosed()){
            if(!file.exists()){
                throw new ExcelException("文件不存在！");
            }
            try {
                tmp = backupFile(file);
                workbook = new SXSSFWorkbook(new XSSFWorkbook(new FileInputStream(tmp.getAbsolutePath())),rowAccessWindowSize);
                sheet = workbook.getSheetAt(0);
            } catch (IOException ex) {
                throw new ExcelException(ex.getMessage());
            }
        }
    }
}
